#undef BLENDSTRETCH
#undef AREAAVERAGING32
#undef AREAAVERAGING64

#define BLENDSTRETCH MAKE_BLEND(BLEND_MODE, PIXEL_BITS, StretchAreaAveraging, SSE2)
#define AREAAVERAGING32 MAKE_BLENDAREAAVERAGING32(BLEND_MODE, PIXEL_BITS, SSE2)
#define AREAAVERAGING64 MAKE_BLENDAREAAVERAGING64(BLEND_MODE, PIXEL_BITS, SSE2)

void AREAAVERAGING64(const int left, const int right, const int top, const int bottom, const DWORD xrIntFloat16, const DWORD yrIntFloat16, PBITMAP lpbDst, PBITMAPEX lpbSrc)
{
    PBYTE pBitsSrc = (BYTE*)lpbSrc->bmBits;
    DWORD nWidthBytesSrc = lpbSrc->bmWidthBytes;

    #pragma omp parallel for if ((right - left) * (bottom - top) > 0x40000) schedule(static) num_threads(omp_get_num_procs() - 1)
    for (int y = top; y < bottom; y++)
    {
        LPXARGB pBitsDst = (LPXARGB)((LPBYTE)lpbDst->bmBits + (y - top) * lpbDst->bmWidthBytes);
        int dY = y * yrIntFloat16 + (yrIntFloat16 >> 1) - 0x8000;
        int t = dY - (yrIntFloat16 >> 1);
        int b = dY + (yrIntFloat16 >> 1);
        int yi1 = (t + 0x8000) >> 16;
        int yi2 = (b + 0x8000) >> 16;
        DWORD h1 = min((yi1 << 16) + 0x8000, b) - max((yi1 << 16) - 0x8000, t);
        DWORD h2 = min((yi2 << 16) + 0x8000, b) - max((yi2 << 16) - 0x8000, t);
        yi1 = max(yi1, 0);
        yi2 = min(yi2, lpbSrc->bmHeight - 1);

        for (int x = left; x < right; x++)
        {
            int dX = x * xrIntFloat16 + (xrIntFloat16 >> 1) - 0x8000;
            int l = dX - (xrIntFloat16 >> 1);
            int r = dX + (xrIntFloat16 >> 1);
            int xi1 = (l + 0x8000) >> 16;
            int xi2 = (r + 0x8000) >> 16;

            DWORD w1 = min((xi1 << 16) + 0x8000, r) - max((xi1 << 16) - 0x8000, l);
            DWORD w2 = min((xi2 << 16) + 0x8000, r) - max((xi2 << 16) - 0x8000, l);
            xi1 = max(xi1, 0);
            xi2 = min(xi2, lpbSrc->bmWidth - 1);

            __m64 ma = _mm_setzero_si64();
            __m64 mr = _mm_setzero_si64();
            __m64 mg = _mm_setzero_si64();
            __m64 mb = _mm_setzero_si64();
            __m64 ms = _mm_setzero_si64();

            PIXEL* pBits;
            pBits = (PIXEL*)(pBitsSrc + yi1 * nWidthBytesSrc);
            AddColorValue64SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi1)), ((UINT64)w1 * (UINT64)h1 + 0xFFFFFF) >> 16, mb, mg, mr, ma, ms);
            AddColorValue64SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi2)), ((UINT64)w2 * (UINT64)h1 + 0xFFFFFF) >> 16, mb, mg, mr, ma, ms);
            pBits = (PIXEL*)(pBitsSrc + yi2 * nWidthBytesSrc);
            AddColorValue64SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi1)), ((UINT64)w1 * (UINT64)h2 + 0xFFFFFF) >> 16, mb, mg, mr, ma, ms);
            AddColorValue64SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi2)), ((UINT64)w2 * (UINT64)h2 + 0xFFFFFF) >> 16, mb, mg, mr, ma, ms);

            for (int j = xi1 + 1; j < xi2; j++)
            {
                pBits = (PIXEL*)(pBitsSrc + yi1 * nWidthBytesSrc);
                AddColorValue64SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + j)), h1, mb, mg, mr, ma, ms);
                pBits = (PIXEL*)(pBitsSrc + yi2 * nWidthBytesSrc);
                AddColorValue64SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + j)), h2, mb, mg, mr, ma, ms);
            }

            for (int i = yi1 + 1; i < yi2; i++)
            {
                pBits = (PIXEL*)(pBitsSrc + i * nWidthBytesSrc);
                AddColorValue64SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi1)), w1, mb, mg, mr, ma, ms);
                AddColorValue64SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi2)), w2, mb, mg, mr, ma, ms);
                for (int j = xi1 + 1; j < xi2; j++)
                {
                    __m64 v0 = _m_from_int(GETPIXEL(lpbSrc, PBYTE(pBits + j)).argb);
                    __m64 v1 = _m_psllqi(v0, 32);
                    __m64 v2 = _m_psllqi(v0, 40);
                    v1 = _m_psrlqi(v1, 48);
                    v2 = _m_psrlqi(v2, 48);
                    ma = _mm_add_si64(ma, v1);
                    mr = _mm_add_si64(mr, v2);
                    v1 = _m_psllqi(v0, 48);
                    v2 = _m_psllqi(v0, 56);
                    v1 = _m_psrlqi(v1, 48);
                    v2 = _m_psrlqi(v2, 48);
                    mg = _mm_add_si64(mg, v1);
                    mb = _mm_add_si64(mb, v2);
                    ms = _mm_add_si64(ms, _m_from_int(256));
                }
            }

            UINT64 s;
            UINT64 aa, rr, gg, bb;

            aa = ma.m64_u64;
            rr = mr.m64_u64;
            gg = mg.m64_u64;
            bb = mb.m64_u64;
            s = ms.m64_u64;

            xARGB clr;
            clr.blue = (BYTE)(bb / s);
            clr.green = (BYTE)(gg / s);
            clr.red = (BYTE)(rr / s);
            clr.alpha = (BYTE)(aa / s);
            BLENDPIXEL(pBitsDst[x - left].argb, clr.argb);
        }
    }
    _m_empty();
}

void AREAAVERAGING32(const int left, const int right, const int top, const int bottom, const DWORD xrIntFloat16, const DWORD yrIntFloat16, PBITMAP lpbDst, PBITMAPEX lpbSrc)
{
    BYTE* pBitsSrc = (BYTE*)lpbSrc->bmBits;
    DWORD nWidthBytesSrc = lpbSrc->bmWidthBytes;

    #pragma omp parallel for if ((right - left) * (bottom - top) > 0x40000) schedule(static) num_threads(omp_get_num_procs() - 1)
    for (int y = top; y < bottom; y++)
    {
        LPXARGB pBitsDst = (LPXARGB)((LPBYTE)lpbDst->bmBits + (y - top) * lpbDst->bmWidthBytes);
        int dY = y * yrIntFloat16 + (yrIntFloat16 >> 1) - 0x8000;
        int t = dY - (yrIntFloat16 >> 1);
        int b = dY + (yrIntFloat16 >> 1);
        int yi1 = (t + 0x8000) >> 16;
        int yi2 = (b + 0x8000) >> 16;
        DWORD h1 = min((yi1 << 16) + 0x8000, b) - max((yi1 << 16) - 0x8000, t);
        DWORD h2 = min((yi2 << 16) + 0x8000, b) - max((yi2 << 16) - 0x8000, t);
        yi1 = max(yi1, 0);
        yi2 = min(yi2, lpbSrc->bmHeight - 1);

        for (int x = left; x < right; x++)
        {
            int dX = x * xrIntFloat16 + (xrIntFloat16 >> 1) - 0x8000;
            int l = dX - (xrIntFloat16 >> 1);
            int r = dX + (xrIntFloat16 >> 1);
            int xi1 = (l + 0x8000) >> 16;
            int xi2 = (r + 0x8000) >> 16;

            DWORD w1 = min((xi1 << 16) + 0x8000, r) - max((xi1 << 16) - 0x8000, l);
            DWORD w2 = min((xi2 << 16) + 0x8000, r) - max((xi2 << 16) - 0x8000, l);
            xi1 = max(xi1, 0);
            xi2 = min(xi2, lpbSrc->bmWidth - 1);

            __m64 ra = _mm_setzero_si64();
            __m64 bg = _mm_setzero_si64();
            DWORD s = 0;

            PIXEL* pBits;
            pBits = (PIXEL*)(pBitsSrc + yi1 * nWidthBytesSrc);
            AddColorValue32SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi1)), ((UINT64)w1 * (UINT64)h1 + 0xFFFFFF) >> 16, bg, ra, s);
            AddColorValue32SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi2)), ((UINT64)w2 * (UINT64)h1 + 0xFFFFFF) >> 16, bg, ra, s);
            pBits = (PIXEL*)(pBitsSrc + yi2 * nWidthBytesSrc);
            AddColorValue32SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi1)), ((UINT64)w1 * (UINT64)h2 + 0xFFFFFF) >> 16, bg, ra, s);
            AddColorValue32SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi2)), ((UINT64)w2 * (UINT64)h2 + 0xFFFFFF) >> 16, bg, ra, s);

            for (int j = xi1 + 1; j < xi2; j++)
            {
                pBits = (PIXEL*)(pBitsSrc + yi1 * nWidthBytesSrc);
                AddColorValue32SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + j)), h1, bg, ra, s);
                pBits = (PIXEL*)(pBitsSrc + yi2 * nWidthBytesSrc);
                AddColorValue32SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + j)), h2, bg, ra, s);
            }

            for (int i = yi1 + 1; i < yi2; i++)
            {
                pBits = (PIXEL*)(pBitsSrc + i * nWidthBytesSrc);
                AddColorValue32SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi1)), w1, bg, ra, s);
                AddColorValue32SSE2(GETPIXEL(lpbSrc, PBYTE(pBits + xi2)), w2, bg, ra, s);
                __m64 mv0 = _mm_setzero_si64();
                __m64 mzero = _mm_setzero_si64();
                for (int j = xi1 + 1; j < xi2; j++)
                {
                    __m64 v = _m_from_int(GETPIXEL(lpbSrc, PBYTE(pBits + j)).argb);
                    v = _m_punpcklbw(v, mzero);
                    s += 256;
                    mv0 = _m_paddw(mv0, v);
                }

                __m64 mv1 = mv0;
                mv0 = _m_punpckhwd(mv0, mzero);
                mv1 = _m_punpcklwd(mv1, mzero);
                mv0 = _m_pslldi(mv0, 8);
                mv1 = _m_pslldi(mv1, 8);
                ra = _m_paddd(ra, mv0);
                bg = _m_paddd(bg, mv1);
            }

            DWORD aa, rr, gg, bb;

            aa = ra.m64_u32[1];
            rr = ra.m64_u32[0];
            gg = bg.m64_u32[1];
            bb = bg.m64_u32[0];

            xARGB clr;
            clr.blue = (BYTE)(bb / s);
            clr.green = (BYTE)(gg / s);
            clr.red = (BYTE)(rr / s);
            clr.alpha = (BYTE)(aa / s);
            BLENDPIXEL(pBitsDst[x - left].argb, clr.argb);
        }
    }
    _m_empty();
}

#define BLENDSTRETCHBICUBIC MAKE_BLEND(BLEND_MODE, PIXEL_BITS, StretchBicubic, )
#define BLENDSTRETCHBICUBICMMX MAKE_BLEND(BLEND_MODE, PIXEL_BITS, StretchBicubic, MMX)

BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc)
{
    if (lpbSrc->bmWidth < lpbDst->bmWidth || lpbSrc->bmHeight < lpbDst->bmHeight)
    {
        if (xCPUInfo::IsSupportMMX())
            return BLENDSTRETCHBICUBICMMX(lpbDst, lpbSrc);
        else
            return BLENDSTRETCHBICUBIC(lpbDst, lpbSrc);
    }
    DWORD xrIntFloat16 = (lpbSrc->bmWidth << 16) / lpbDst->bmWidth;
    DWORD yrIntFloat16 = (lpbSrc->bmHeight << 16) / lpbDst->bmHeight;

    if (xrIntFloat16 > 0x100000 || yrIntFloat16 > 0x100000)
    {
        AREAAVERAGING64(0, lpbDst->bmWidth, 0, lpbDst->bmHeight, xrIntFloat16, yrIntFloat16, lpbDst, lpbSrc);
    }
    else
    {
        AREAAVERAGING32(0, lpbDst->bmWidth, 0, lpbDst->bmHeight, xrIntFloat16, yrIntFloat16, lpbDst, lpbSrc);
    }
    return TRUE;
}

BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc, LPCSIZE lpszDst, LPCRECT lprcClipped)
{
    if (lpbSrc->bmWidth < lpszDst->cx || lpbSrc->bmHeight < lpszDst->cy)
    {
        if (xCPUInfo::IsSupportMMX())
            return BLENDSTRETCHBICUBICMMX(lpbDst, lpbSrc, lpszDst, lprcClipped);
        else
            return BLENDSTRETCHBICUBIC(lpbDst, lpbSrc, lpszDst, lprcClipped);

    }
    DWORD xrIntFloat16 = (lpbSrc->bmWidth << 16) / lpszDst->cx;
    DWORD yrIntFloat16 = (lpbSrc->bmHeight << 16) / lpszDst->cy;

    if (xrIntFloat16 > 0x100000 || yrIntFloat16 > 0x100000)
    {
        AREAAVERAGING64(lprcClipped->left, lprcClipped->right, lprcClipped->top, lprcClipped->bottom, xrIntFloat16, yrIntFloat16, lpbDst, lpbSrc);
    }
    else
    {
        AREAAVERAGING32(lprcClipped->left, lprcClipped->right, lprcClipped->top, lprcClipped->bottom, xrIntFloat16, yrIntFloat16, lpbDst, lpbSrc);
    }
    return TRUE;
}

#undef BLENDSTRETCHBICUBIC